#include <aphw/Database.hpp>

#include <vector>

#include <boost/foreach.hpp>
#include <boost/python.hpp>
#include <ham/hamsterdb.h>

#include <aphw/Exceptions.hpp>
#include <aphw/Key.hpp>
#include <aphw/Record.hpp>
#include <aphw/Utils.hpp>

using namespace boost::python;

namespace
{

ham_status_t open_db(aphw::Database& db, 
                     const std::string& filename,
                     ham_u32_t flags,
                     dict params)
{
    std::vector<ham_parameter_t> paramvec =
        aphw::constructParams(params);

    return ham_open_ex(db.db(),
                       filename.c_str(),
                       flags,
                       &paramvec[0]);
}

ham_status_t create_db(
    aphw::Database& db,
    const std::string& filename,
    ham_u32_t flags,
    dict params)
    // ham_u32_t mode)
{
    // TODO: Expose mode stuff?

    std::vector<ham_parameter_t> paramvec =
        aphw::constructParams(params);

    return ham_create_ex(db.db(),
                         filename.c_str(),
                         flags,
                         S_IRWXU | S_IRWXG,
                         &paramvec[0]);
}

ham_status_t create_memory_db(
    aphw::Database& db,
    ham_u32_t flags,
    dict params)
    // ham_u32_t mode)
{
    // TODO: Expose mode stuff?

    std::vector<ham_parameter_t> paramvec =
        aphw::constructParams(params);

    return ham_create_ex(db.db(),
                         NULL,
                         flags,
                         S_IRWXU | S_IRWXG,
                         &paramvec[0]);
}

ham_status_t close_db(aphw::Database& db,
                      ham_u32_t flags)
{
    return ham_close(db.db(),
                     flags);
}

ham_status_t insert_db(aphw::Database& db,
                       aphw::Key& key,
                       aphw::SetRecord& rec,
                       ham_u32_t flags,
                       ham_u32_t partial_offset,
                       ham_u32_t partial_size)
{
    rec.rec()->partial_offset = partial_offset;
    rec.rec()->partial_size = partial_size;

    // TODO: This sucks. Right now, a partial write requires that you
    // know the full length of the existing record if you want to
    // write some sub-part of it. It would be nice if you could just
    // specify where and what you want to write without worrying about
    // the size...hamster could zero-pad as needed.
    if (flags & HAM_PARTIAL)
    {
        aphw::GetRecord grec;
        ham_find(db.db(),
                 *db.txn(),
                 key.key(),
                 grec.rec(),
                 0);
        rec.rec()->size = grec.rec()->size;
    }

    return ham_insert(db.db(),
                      *db.txn(),
                      key.key(),
                      rec.rec(),
                      flags);
}

ham_status_t find_db(aphw::Database& db,
                     aphw::Key& key,
                     aphw::GetRecord& rec,
                     ham_u32_t flags,
                     ham_u32_t partial_offset,
                     ham_u32_t partial_size)
{
    rec.rec()->partial_offset = partial_offset;
    rec.rec()->partial_size = partial_size;

    return ham_find(db.db(),
                    *db.txn(),
                    key.key(),
                    rec.rec(),
                    flags);
}

ham_status_t erase_db(aphw::Database& db,
                      aphw::Key& key,
                      ham_u32_t flags)
{
    return ham_erase(db.db(),
                     *db.txn(),
                     key.key(),
                     flags);
}

ham_status_t flush_db(aphw::Database& db,
                      ham_u32_t flags)
{
    return ham_flush(db.db(), flags);
}

tuple key_count(aphw::Database& db,
                ham_u32_t flags)
{
    ham_offset_t count;
    ham_status_t err = 
        ham_get_key_count(
            db.db(),
            *db.txn(),
            flags,
            &count);

    return make_tuple(err, count);
}


tuple get_params(aphw::Database& db,
                 list params)
{
    std::vector<ham_parameter_t> paramvec;
    boost::python::ssize_t numParams = len(params);
    
    for (boost::python::ssize_t paramIdx = 0;
         paramIdx < numParams;
         ++paramIdx)
    {
        ham_parameter_t paramValue;
        paramValue.name = extract<ham_u32_t>(params[paramIdx]);
        paramValue.value = 0;
        paramvec.push_back(paramValue);
    }

    ham_parameter_t paramValue = {0,0};
    paramvec.push_back(paramValue);

    ham_status_t err =
        ham_get_parameters(db.db(),
                           &paramvec[0]);
    
    boost::python::dict rval;

    if (HAM_SUCCESS == err)
    {
        BOOST_FOREACH(const ham_parameter_t& param, paramvec)
        {
            if (param.name != 0)
                rval[param.name] = param.value;
        }
    }
    
    return make_tuple(err, rval);
}

ham_u32_t get_flags(aphw::Database& db) {
    return ham_get_flags(db.db());
}

ham_status_t get_error(aphw::Database& db) {
    return ham_get_error(db.db());
}

}

namespace aphw
{

struct Database::Body
{
    Body() :
        db_ (NULL),
        txn_ (NULL)
        {
            ham_status_t err = ham_new(&db_);
            if (HAM_OUT_OF_MEMORY == err)
                throw OutOfMemoryError();
            
            assert(db_);
        }

    ~Body()
        {
            assert(db_);
            ham_status_t err = ham_delete(db_);
            assert(HAM_SUCCESS == err);
            
            // TODO: Transaction? Or is that auto-handled?
        }

    ham_db_t* db_;
    ham_txn_t* txn_;
};

Database::Database() :
    body_ (new Body())
{
}

ham_db_t* Database::db()
{
    assert(body_);
    assert(body_->db_);

    return body_->db_;
}

ham_txn_t** Database::txn()
{
    assert(body_);
    
    return &(body_->txn_);
}

void init_Database()
{
    class_<Database>("Database", init<>())
        ;

    def("open", ::open_db);
    def("create", ::create_db);
    def("create_memory", ::create_memory_db);
    def("close", ::close_db);
    def("insert", ::insert_db);
    def("find", ::find_db);
    def("erase", ::erase_db);
    def("flush", ::flush_db);
    def("key_count", ::key_count);
    def("get_params", ::get_params);
    def("get_flags", ::get_flags);
    def("get_error", ::get_error);
}

}
